summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbunnei <bunneidev@gmail.com>2023-02-12 09:17:19 +0100
committerbunnei <bunneidev@gmail.com>2023-06-03 09:05:30 +0200
commit0e52d11ede15d0a47761dce42ebf1a8f7eb83719 (patch)
tree5ce614dc7aaa5c7251733f10dcda141baf33ae4b
parentandroid: Replace notification icon with yuzu (diff)
downloadyuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar
yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.gz
yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.bz2
yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.lz
yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.xz
yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.zst
yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.zip
-rw-r--r--src/android/app/build.gradle2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java6
-rw-r--r--src/android/app/src/main/jni/native.cpp211
-rw-r--r--src/android/app/src/main/jni/native.h8
-rw-r--r--src/android/app/src/main/res/layout/card_game.xml119
-rw-r--r--src/android/app/src/main/res/layout/fragment_grid.xml23
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
15 files changed, 272 insertions, 174 deletions
diff --git a/src/android/app/build.gradle b/src/android/app/build.gradle
index c516b2bff..74835113c 100644
--- a/src/android/app/build.gradle
+++ b/src/android/app/build.gradle
@@ -11,7 +11,7 @@ def abiFilter = "arm64-v8a" //, "x86"
android {
compileSdkVersion 32
- ndkVersion "25.1.8937393"
+ ndkVersion "25.2.9519653"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java
index 75395bd4c..7a1ddd38e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java
@@ -141,9 +141,9 @@ public final class NativeLibrary {
* Gets the embedded icon within the given ROM.
*
* @param filename the file path to the ROM.
- * @return an integer array containing the color data for the icon.
+ * @return a byte array containing the JPEG data for the icon.
*/
- public static native int[] GetIcon(String filename);
+ public static native byte[] GetIcon(String filename);
/**
* Gets the embedded title of the given ISO/ROM.
@@ -205,6 +205,11 @@ public final class NativeLibrary {
public static native void StopEmulation();
/**
+ * Resets the in-memory ROM metadata cache.
+ */
+ public static native void ResetRomMetadata();
+
+ /**
* Returns true if emulation is running (or is paused).
*/
public static native boolean IsRunning();
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java
index cd9f823d4..ed1a000c7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java
@@ -86,11 +86,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
mCursor.getString(GameDatabase.GAME_COLUMN_PATH));
holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " "));
- holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
-
- String filepath = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
- String filename = FileUtil.getFilename(YuzuApplication.getAppContext(), filepath);
- holder.textFileName.setText(filename);
+ holder.textGameCaption.setText(mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION));
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID);
@@ -98,7 +94,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE);
holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION);
holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS);
- holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY);
+ holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION);
final int backgroundColorId = isValidGame(holder.path) ? R.color.view_background : R.color.view_disabled;
View itemView = holder.getItemView();
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java
index bc1b19bd1..681117268 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java
@@ -47,7 +47,7 @@ public final class Game {
cursor.getString(GameDatabase.GAME_COLUMN_REGIONS),
cursor.getString(GameDatabase.GAME_COLUMN_PATH),
cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID),
- cursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
+ cursor.getString(GameDatabase.GAME_COLUMN_CAPTION));
}
public String getTitle() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java
index 771e35c69..a10ac6ff2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java
@@ -29,7 +29,7 @@ public final class GameDatabase extends SQLiteOpenHelper {
public static final int GAME_COLUMN_DESCRIPTION = 3;
public static final int GAME_COLUMN_REGIONS = 4;
public static final int GAME_COLUMN_GAME_ID = 5;
- public static final int GAME_COLUMN_COMPANY = 6;
+ public static final int GAME_COLUMN_CAPTION = 6;
public static final int FOLDER_COLUMN_PATH = 1;
public static final String KEY_DB_ID = "_id";
public static final String KEY_GAME_PATH = "path";
@@ -176,6 +176,9 @@ public final class GameDatabase extends SQLiteOpenHelper {
return;
}
+ // Ensure keys are loaded so that ROM metadata can be decrypted.
+ NativeLibrary.ReloadKeys();
+
MinimalDocumentFile[] children = FileUtil.listFiles(context, parent);
for (MinimalDocumentFile file : children) {
if (file.isDirectory()) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java
index 7fdd692c2..552232bd3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java
@@ -161,6 +161,7 @@ public final class MainActivity extends AppCompatActivity implements MainView {
if (FileUtil.copyUriToInternalStorage(this, result.getData(), dstPath, "prod.keys")) {
if (NativeLibrary.ReloadKeys()) {
Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show();
+ refreshFragment();
} else {
Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show();
launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS);
@@ -184,6 +185,7 @@ public final class MainActivity extends AppCompatActivity implements MainView {
private void refreshFragment() {
if (mPlatformGamesFragment != null) {
+ NativeLibrary.ResetRomMetadata();
mPlatformGamesFragment.refresh();
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java
index 6c327b1b8..2d74f43ca 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java
@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
@@ -13,6 +14,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import org.yuzu.yuzu_emu.NativeLibrary;
import org.yuzu.yuzu_emu.YuzuApplication;
import org.yuzu.yuzu_emu.R;
import org.yuzu.yuzu_emu.adapters.GameAdapter;
@@ -43,19 +45,34 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
- int columns = getResources().getInteger(R.integer.game_grid_columns);
- RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
mAdapter = new GameAdapter();
- mRecyclerView.setLayoutManager(layoutManager);
- mRecyclerView.setAdapter(mAdapter);
- mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(ContextCompat.getDrawable(getActivity(), R.drawable.gamelist_divider), 1));
+ // Organize our grid layout based on the current view.
+ if (isAdded()) {
+ view.getViewTreeObserver()
+ .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (view.getMeasuredWidth() == 0) {
+ return;
+ }
+
+ int columns = view.getMeasuredWidth() /
+ requireContext().getResources().getDimensionPixelSize(R.dimen.card_width);
+ if (columns == 0) {
+ columns = 1;
+ }
+ view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
+ mRecyclerView.setLayoutManager(layoutManager);
+ mRecyclerView.setAdapter(mAdapter);
+ }
+ });
+ }
// Add swipe down to refresh gesture
- final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.refresh_grid_games);
+ final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.swipe_refresh);
pullToRefresh.setOnRefreshListener(() -> {
- GameDatabase databaseHelper = YuzuApplication.databaseHelper;
- databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
refresh();
pullToRefresh.setRefreshing(false);
});
@@ -63,6 +80,8 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
@Override
public void refresh() {
+ GameDatabase databaseHelper = YuzuApplication.databaseHelper;
+ databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
mPresenter.refresh();
updateTextView();
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java
index b75dc9a62..fd43575de 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java
@@ -1,6 +1,7 @@
package org.yuzu.yuzu_emu.utils;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Request;
@@ -13,15 +14,16 @@ import java.nio.IntBuffer;
public class GameIconRequestHandler extends RequestHandler {
@Override
public boolean canHandleRequest(Request data) {
- return "iso".equals(data.uri.getScheme());
+ return "content".equals(data.uri.getScheme());
}
@Override
public Result load(Request request, int networkPolicy) {
- String url = request.uri.getHost() + request.uri.getPath();
- int[] vector = NativeLibrary.GetIcon(url);
- Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565);
- bitmap.copyPixelsFromBuffer(IntBuffer.wrap(vector));
+ String gamePath = request.uri.toString();
+ byte[] data = NativeLibrary.GetIcon(gamePath);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
+ Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
return new Result(bitmap, Picasso.LoadedFrom.DISK);
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java
index 5033691b3..504dc5b6d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java
@@ -31,7 +31,7 @@ public class PicassoUtils {
public static void loadGameIcon(ImageView imageView, String gamePath) {
Picasso
.get()
- .load(Uri.parse("iso:/" + gamePath))
+ .load(Uri.parse(gamePath))
.fit()
.centerInside()
.config(Bitmap.Config.RGB_565)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java
index 2dc0f34f3..41b8c6a27 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java
@@ -16,8 +16,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder {
private View itemView;
public ImageView imageIcon;
public TextView textGameTitle;
- public TextView textCompany;
- public TextView textFileName;
+ public TextView textGameCaption;
public String gameId;
@@ -36,8 +35,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder {
imageIcon = itemView.findViewById(R.id.image_game_screen);
textGameTitle = itemView.findViewById(R.id.text_game_title);
- textCompany = itemView.findViewById(R.id.text_company);
- textFileName = itemView.findViewById(R.id.text_filename);
+ textGameCaption = itemView.findViewById(R.id.text_game_caption);
}
public View getItemView() {
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 6d1e75c40..2bd908308 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -24,6 +24,7 @@
#include "core/file_sys/vfs_real.h"
#include "core/hid/hid_core.h"
#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "jni/config.h"
#include "jni/emu_window/emu_window.h"
@@ -34,7 +35,11 @@ namespace {
class EmulationSession final {
public:
- EmulationSession() = default;
+ EmulationSession() {
+ m_system.Initialize();
+ m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
+ }
+
~EmulationSession() = default;
static EmulationSession& GetInstance() {
@@ -42,151 +47,205 @@ public:
}
const Core::System& System() const {
- return system;
+ return m_system;
}
Core::System& System() {
- return system;
+ return m_system;
}
const EmuWindow_Android& Window() const {
- return *window;
+ return *m_window;
}
EmuWindow_Android& Window() {
- return *window;
+ return *m_window;
}
ANativeWindow* NativeWindow() const {
- return native_window;
+ return m_native_window;
}
- void SetNativeWindow(ANativeWindow* native_window_) {
- native_window = native_window_;
+ void SetNativeWindow(ANativeWindow* m_native_window_) {
+ m_native_window = m_native_window_;
}
bool IsRunning() const {
- std::scoped_lock lock(mutex);
- return is_running;
+ std::scoped_lock lock(m_mutex);
+ return m_is_running;
}
const Core::PerfStatsResults& PerfStats() const {
- std::scoped_lock perf_stats_lock(perf_stats_mutex);
- return perf_stats;
+ std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
+ return m_perf_stats;
}
void SurfaceChanged() {
if (!IsRunning()) {
return;
}
- window->OnSurfaceChanged(native_window);
+ m_window->OnSurfaceChanged(m_native_window);
}
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
- std::scoped_lock lock(mutex);
+ std::scoped_lock lock(m_mutex);
// Loads the configuration.
Config{};
// Create the render window.
- window = std::make_unique<EmuWindow_Android>(&input_subsystem, native_window);
+ m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window);
// Initialize system.
- system.SetShuttingDown(false);
- system.Initialize();
- system.ApplySettings();
- system.HIDCore().ReloadInputDevices();
- system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
- system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
+ m_system.SetShuttingDown(false);
+ m_system.Initialize();
+ m_system.ApplySettings();
+ m_system.HIDCore().ReloadInputDevices();
+ m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
+ m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
+ m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem());
// Load the ROM.
- load_result = system.Load(EmulationSession::GetInstance().Window(), filepath);
- if (load_result != Core::SystemResultStatus::Success) {
- return load_result;
+ m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
+ if (m_load_result != Core::SystemResultStatus::Success) {
+ return m_load_result;
}
// Complete initialization.
- system.GPU().Start();
- system.GetCpuManager().OnGpuReady();
- system.RegisterExitCallback([&] { HaltEmulation(); });
+ m_system.GPU().Start();
+ m_system.GetCpuManager().OnGpuReady();
+ m_system.RegisterExitCallback([&] { HaltEmulation(); });
return Core::SystemResultStatus::Success;
}
void ShutdownEmulation() {
- std::scoped_lock lock(mutex);
+ std::scoped_lock lock(m_mutex);
- is_running = false;
+ m_is_running = false;
// Unload user input.
- system.HIDCore().UnloadInputDevices();
+ m_system.HIDCore().UnloadInputDevices();
// Shutdown the main emulated process
- if (load_result == Core::SystemResultStatus::Success) {
- system.DetachDebugger();
- system.ShutdownMainProcess();
- detached_tasks.WaitForAllTasks();
- load_result = Core::SystemResultStatus::ErrorNotInitialized;
+ if (m_load_result == Core::SystemResultStatus::Success) {
+ m_system.DetachDebugger();
+ m_system.ShutdownMainProcess();
+ m_detached_tasks.WaitForAllTasks();
+ m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
}
// Tear down the render window.
- window.reset();
+ m_window.reset();
+ }
+
+ void PauseEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_system.Pause();
+ }
+
+ void UnPauseEmulation() {
+ std::scoped_lock lock(m_mutex);
+ m_system.Run();
}
void HaltEmulation() {
- std::scoped_lock lock(mutex);
- is_running = false;
- cv.notify_one();
+ std::scoped_lock lock(m_mutex);
+ m_is_running = false;
+ m_cv.notify_one();
}
void RunEmulation() {
{
- std::scoped_lock lock(mutex);
- is_running = true;
+ std::scoped_lock lock(m_mutex);
+ m_is_running = true;
}
- void(system.Run());
+ void(m_system.Run());
- if (system.DebuggerEnabled()) {
- system.InitializeDebugger();
+ if (m_system.DebuggerEnabled()) {
+ m_system.InitializeDebugger();
}
while (true) {
{
- std::unique_lock lock(mutex);
- if (cv.wait_for(lock, std::chrono::milliseconds(100),
- [&]() { return !is_running; })) {
+ std::unique_lock lock(m_mutex);
+ if (m_cv.wait_for(lock, std::chrono::milliseconds(100),
+ [&]() { return !m_is_running; })) {
// Emulation halted.
break;
}
}
{
// Refresh performance stats.
- std::scoped_lock perf_stats_lock(perf_stats_mutex);
- perf_stats = system.GetAndResetPerfStats();
+ std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
+ m_perf_stats = m_system.GetAndResetPerfStats();
}
}
}
+ std::string GetRomTitle(const std::string& path) {
+ return GetRomMetadata(path).title;
+ }
+
+ std::vector<u8> GetRomIcon(const std::string& path) {
+ return GetRomMetadata(path).icon;
+ }
+
+ void ResetRomMetadata() {
+ m_rom_metadata_cache.clear();
+ }
+
private:
- static EmulationSession s_instance;
+ struct RomMetadata {
+ std::string title;
+ std::vector<u8> icon;
+ };
+
+ RomMetadata GetRomMetadata(const std::string& path) {
+ if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
+ return search->second;
+ }
+
+ return CacheRomMetadata(path);
+ }
- ANativeWindow* native_window{};
+ RomMetadata CacheRomMetadata(const std::string& path) {
+ const auto file = Core::GetGameFileFromPath(m_vfs, path);
+ const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
- InputCommon::InputSubsystem input_subsystem;
- Common::DetachedTasks detached_tasks;
- Core::System system;
+ RomMetadata entry;
+ loader->ReadTitle(entry.title);
+ loader->ReadIcon(entry.icon);
- Core::PerfStatsResults perf_stats{};
+ m_rom_metadata_cache[path] = entry;
- std::unique_ptr<EmuWindow_Android> window;
- std::condition_variable_any cv;
- bool is_running{};
- Core::SystemResultStatus load_result{Core::SystemResultStatus::ErrorNotInitialized};
+ return entry;
+ }
+
+private:
+ static EmulationSession s_instance;
- mutable std::mutex perf_stats_mutex;
- mutable std::mutex mutex;
+ // Frontend management
+ std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
+
+ // Window management
+ std::unique_ptr<EmuWindow_Android> m_window;
+ ANativeWindow* m_native_window{};
+
+ // Core emulation
+ Core::System m_system;
+ InputCommon::InputSubsystem m_input_subsystem;
+ Common::DetachedTasks m_detached_tasks;
+ Core::PerfStatsResults m_perf_stats{};
+ std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs;
+ Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
+ bool m_is_running{};
+
+ // Synchronization
+ std::condition_variable_any m_cv;
+ mutable std::mutex m_perf_stats_mutex;
+ mutable std::mutex m_mutex;
};
/*static*/ EmulationSession EmulationSession::s_instance;
@@ -275,16 +334,25 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation([[maybe_unused]] JNIEnv* env,
- [[maybe_unused]] jclass clazz) {}
+ [[maybe_unused]] jclass clazz) {
+ EmulationSession::GetInstance().UnPauseEmulation();
+}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation([[maybe_unused]] JNIEnv* env,
- [[maybe_unused]] jclass clazz) {}
+ [[maybe_unused]] jclass clazz) {
+ EmulationSession::GetInstance().PauseEmulation();
+}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz) {
EmulationSession::GetInstance().HaltEmulation();
}
+void Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata([[maybe_unused]] JNIEnv* env,
+ [[maybe_unused]] jclass clazz) {
+ EmulationSession::GetInstance().ResetRomMetadata();
+}
+
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
@@ -347,16 +415,21 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv*
}
}
-jintArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env,
- [[maybe_unused]] jclass clazz,
- [[maybe_unused]] jstring j_file) {
- return {};
+jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env,
+ [[maybe_unused]] jclass clazz,
+ [[maybe_unused]] jstring j_filename) {
+ auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
+ jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
+ env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
+ reinterpret_cast<jbyte*>(icon_data.data()));
+ return icon;
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass clazz,
[[maybe_unused]] jstring j_filename) {
- return env->NewStringUTF("");
+ auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
+ return env->NewStringUTF(title.c_str());
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription([[maybe_unused]] JNIEnv* env,
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 210976201..bbc783aa8 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -19,6 +19,9 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIE
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
jclass clazz);
+JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
+ jclass clazz);
+
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
jclass clazz);
@@ -39,8 +42,9 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JN
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
jfloat x, jfloat y);
-JNIEXPORT jintArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env, jclass clazz,
- jstring j_file);
+JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
+ jclass clazz,
+ jstring j_file);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
jstring j_filename);
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml
index 217f02d34..a0d453719 100644
--- a/src/android/app/src/main/res/layout/card_game.xml
+++ b/src/android/app/src/main/res/layout/card_game.xml
@@ -1,81 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
android:clickable="true"
+ android:clipToPadding="false"
android:focusable="true"
- android:foreground="?android:attr/selectableItemBackground"
- android:transitionName="card_game"
- tools:layout_width="match_parent">
+ android:paddingStart="4dp"
+ android:paddingTop="8dp"
+ android:paddingEnd="4dp"
+ android:paddingBottom="8dp"
+ android:transitionName="card_game">
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/linearLayout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="8dp">
+ <androidx.cardview.widget.CardView
+ android:id="@+id/card_game_art"
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ app:cardCornerRadius="4dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/image_game_screen"
- android:layout_width="56dp"
- android:layout_height="56dp"
- android:adjustViewBounds="false"
- android:cropToPadding="false"
- android:scaleType="fitCenter"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- tools:scaleType="fitCenter" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
<TextView
- android:id="@+id/text_game_title"
+ android:id="@+id/text_game_title_inner"
style="@android:style/TextAppearance.Material.Subhead"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:baselineAligned="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:ellipsize="end"
- android:gravity="center_vertical"
- android:lines="1"
- android:maxLines="1"
- android:textAlignment="viewStart"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/image_game_screen"
- app:layout_constraintTop_toTopOf="parent"
- tools:text="The Legend of Zelda\nOcarina of Time 3D"
- android:textColor="@color/header_text" />
+ android:gravity="center|top"
+ android:maxLines="2"
+ android:paddingLeft="2dp"
+ android:paddingRight="2dp"
+ android:paddingTop="8dp"
+ android:visibility="visible"
+ tools:text="The Legend of Zelda: The Wind Waker" />
- <TextView
- android:id="@+id/text_company"
- style="@android:style/TextAppearance.Material.Caption"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:lines="1"
- android:maxLines="1"
- app:layout_constraintBottom_toBottomOf="@+id/image_game_screen"
- app:layout_constraintStart_toStartOf="@+id/text_game_title"
- app:layout_constraintTop_toBottomOf="@+id/text_game_title"
- app:layout_constraintVertical_bias="0.842"
- tools:text="Nintendo"
- android:textColor="@color/header_subtext" />
+ </androidx.cardview.widget.CardView>
- <TextView
- android:id="@+id/text_filename"
- style="@android:style/TextAppearance.Material.Caption"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:lines="1"
- android:maxLines="1"
- app:layout_constraintBottom_toBottomOf="@+id/image_game_screen"
- app:layout_constraintStart_toStartOf="@+id/text_game_title"
- app:layout_constraintTop_toBottomOf="@+id/text_game_title"
- app:layout_constraintVertical_bias="0.0"
- tools:text="Pilotwings_Resort.cxi"
- android:textColor="@color/header_subtext" />
+ <TextView
+ android:id="@+id/text_game_title"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:paddingTop="8dp"
+ app:layout_constraintEnd_toEndOf="@+id/card_game_art"
+ app:layout_constraintStart_toStartOf="@+id/card_game_art"
+ app:layout_constraintTop_toBottomOf="@+id/card_game_art"
+ tools:text="The Legend of Zelda: The Wind Waker" />
- </androidx.constraintlayout.widget.ConstraintLayout>
+ <TextView
+ android:id="@+id/text_game_caption"
+ style="@android:style/TextAppearance.Material.Caption"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:lines="1"
+ android:maxLines="1"
+ android:paddingTop="8dp"
+ app:layout_constraintEnd_toEndOf="@+id/card_game_art"
+ app:layout_constraintStart_toStartOf="@+id/card_game_art"
+ app:layout_constraintTop_toBottomOf="@+id/text_game_title"
+ tools:text="Nintendo" />
-</androidx.cardview.widget.CardView>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_grid.xml b/src/android/app/src/main/res/layout/fragment_grid.xml
index f5b6c2e19..01399e18d 100644
--- a/src/android/app/src/main/res/layout/fragment_grid.xml
+++ b/src/android/app/src/main/res/layout/fragment_grid.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- android:id="@+id/refresh_grid_games"
- android:layout_width="match_parent"
+ android:id="@+id/swipe_refresh"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
@@ -22,12 +22,13 @@
android:textSize="18sp"
android:gravity="center" />
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/grid_games"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:listitem="@layout/card_game" />
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/grid_games"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ tools:listitem="@layout/card_game" />
</RelativeLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
-</FrameLayout> \ No newline at end of file
+</FrameLayout>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index a3bb0c2c5..0b028a167 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -6,7 +6,7 @@
<dimen name="spacing_list">64dp</dimen>
<dimen name="spacing_fab">72dp</dimen>
<dimen name="menu_width">256dp</dimen>
- <dimen name="card_width">135dp</dimen>
+ <dimen name="card_width">150dp</dimen>
<dimen name="dialog_margin">20dp</dimen>
<dimen name="elevated_app_bar">3dp</dimen>